home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C
/
Snippets
/
Stuart's Tech Notes
/
Debugging 'orphan' code
< prev
next >
Wrap
Text File
|
1995-09-17
|
3KB
|
80 lines
// Debugging 'orphan' code
// (C) 1995 Stuart Cheshire <cheshire@cs.stanford.edu>
Did you get stuck trying to write your first trap-patching INIT?
Did you put in DebugStr calls which just printed garbage in MacsBug?
Well, I did, and it made the legendary Macintosh learning curve even
steeper.
Applications on the Mac usually have register A5 set up to point to
their global variables. Standalone code resources often use register
A4 for a similar purpose. 'Orphan' code is code that doesn't have
either A4 or A5 set up to point at it's globals. Trap patches,
completion routines and AppleTalk socket listeners are all 'orphan'
code that cannot access their global variables until they manually
set up the correct base address value in the register.
The problem is that even innocent-looking things such as strings for
DebugStr calls count as global variables and require the base register
to be set up correctly. Even in a standalone code resource, Think C
references all static data as an offset from a base register. Why
it doesn't use PC-relative references is a mystery. (A PC-relative
reference is where the compiler uses instructions that say things
like "the address 112 bytes before the address of this instruction
in memory".) No matter where a standalone code resource is loaded in
memory, a PC-relative reference will always be correct. Think C uses
references relative to register A4, which means that you have to make
sure register A4 is set up before you do anything which depends on it.
The catch-22 for learning programmers trying to debug their code to find
out why register A4 is not set up correctly is that in Think C the DebugStr
call doesn't work unless you already have register A4 set up correctly.
Even for experienced programmers, sometimes you want to be able to put
in a DebugStr breakpoint without having to go to all the effort of
setting up A4 and then restoring it afterwards. That's why I wrote the
FixAddress routine:
// Fixes an (erroneous) A4 relative address on top of stack
static void FixAddress()
{
asm { movem.l d0/a4, -(sp) ; save registers
move.l a4, d0
sub.l d0, 12(sp) ; subtract incorrect base from argument
jsr __GetA4
move.l (a4), d0 ; get correct a4 value
add.l d0, 12(sp) ; add correct base address to argument
movem.l (sp)+, d0/a4
}
}
The way you use it is like this:
pea "\pThis is a Breakpoint"
bsr FixAddress
_DebugStr
The pea instruction pushes the address of the string on the stack, as a
relative offset from the address in register A4 (which may be incorrect).
The call to FixAddress then repairs this address, if it was wrong, so
that the following DebugStr call will work correctly.
This assumes that __GetA4 is defined as:
static void __GetA4(void)
{
asm { bsr.s @1
dc.l 0 ; store A4 here
@1 move.l (sp)+, a4 ; A4 contains the address of my A4 value
}
}
If the __GetA4 routine your code uses returns the address in a different
register (eg. a1), then you must change FixAddress to use that register,
eg.
move.l (a1), d0 ; get correct a4 value
Obviously for application code, you should change this to use register A5
instead of A4.
Share and enjoy.